package com.glsc.ngateway.platform.service.itflow.aspect;

import com.glsc.ngateway.common.api.common.enums.PlatformConstant;
import com.glsc.ngateway.common.api.flowable.dto.param.ParamCopyUserDto;
import com.glsc.ngateway.common.api.flowable.dto.param.ParamStopDto;
import com.glsc.ngateway.common.api.flowable.dto.task.doTaskParam.AbstractCreateParamDto;
import com.glsc.ngateway.common.api.flowable.dto.task.doTaskParam.AbstractFindByIdParamDto;
import com.glsc.ngateway.common.api.flowable.dto.task.doTaskParam.AbstractPassParamDto;
import com.glsc.ngateway.common.api.flowable.dto.task.doTaskParam.AbstractRejectParamDto;
import com.glsc.ngateway.common.api.flowable.feign.IFeignFlowableService;
import com.glsc.ngateway.common.api.flowable.resp.FlowableResponse;
import com.glsc.ngateway.common.api.flowable.resp.ResponseCopyUserDto;
import com.glsc.ngateway.common.api.flowable.resp.ResponseSituationDto;
import com.glsc.ngateway.common.api.platform.dto.user.PlatformUserDto;
import com.glsc.ngateway.common.base.enums.UserTypeEnum;
import com.glsc.ngateway.common.base.exception.PlatformException;
import com.glsc.ngateway.platform.service.FlowCommonService;
import com.glsc.ngateway.platform.service.UserService;
import com.glsc.ngateway.platform.utils.RequestTool;
import com.netflix.discovery.shared.Pair;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Objects;

/**
 * ItFlowService切面
 *
 * @author xuchang
 */
@Component
@Aspect
public class ItFlowServiceAspect {

    private static final Logger logger = LoggerFactory.getLogger(ItFlowServiceAspect.class);

    @Resource
    private RequestTool requestTool;

    @Resource
    private FlowCommonService flowCommonService;

    @Resource
    private UserService userService;

    @Resource
    private IFeignFlowableService iFeignFlowableService;

    /**
     * 切点，对于注解ItFlowAuthentication进行切入
     */
    @Pointcut("@annotation(com.glsc.ngateway.platform.service.itflow.aspect.ItFlowAuthentication)")
    public void authenticationPointCut() {
    }

    /**
     * 切面，环绕
     */
    @Around("authenticationPointCut()")
    public Object authenticationAround(ProceedingJoinPoint point) throws PlatformException {
        String account = requestTool.getAccountFromRequest();
        if (Strings.isBlank(account)) {
            throw PlatformException.error("未找到操作人员信息", false, false);
        }
        PlatformUserDto user = userService.findByUserAccount(account);
        if (Objects.isNull(user)) {
            throw PlatformException.error("未找到操作人员信息", false, false);
        }
        //非管理员进行权限校验
        if (!UserTypeEnum.SYS.getCode().equals(user.getUserType())) {
            //<processId, taskId>
            Pair<String, String> pair = buildParam(point.getArgs());
            //查看流程是否与我有关，无关则鉴权失败
            if (Strings.isBlank(account)) {
                throw PlatformException.error("鉴权失败：未找到操作人员信息", false, false);
            }
            ResponseSituationDto flowInfo = null;
            //根据processId找到流程信息
            if (Strings.isNotBlank(pair.first())) {
                flowInfo = flowCommonService.getProcessInfo(pair.first());
            } else if (Strings.isNotBlank(pair.second())) {
                flowInfo = flowCommonService.getTaskInfo(pair.second(), PlatformConstant.SYS_ID_GATEWAY).getData();
            } else {
                throw PlatformException.error("鉴权失败：未解析到参数", false, false);
            }
            if (Objects.isNull(flowInfo)) {
                throw PlatformException.error("未找到流程信息或流程已审批", false, false);
            } else if (checkAboutFailed(flowInfo, account)) {
                throw PlatformException.error("无权限", false, false);
            }
        }
        Object result = null;
        try {
            result = point.proceed();
        } catch (PlatformException e) {
            throw e;
        } catch (Throwable e) {
            logger.error("异常", e);
            throw PlatformException.error(e.getMessage());
        }
        return result;
    }

    /**
     * 检查流程是否相关
     */
    private boolean checkAboutFailed(ResponseSituationDto flowInfo, String account) {
        boolean notAbout = true;
        //检查历史节点
        if (CollectionUtils.isNotEmpty(flowInfo.getCreatedTaskInfoDtoList())) {
            notAbout = flowInfo.getCreatedTaskInfoDtoList()
                    .stream()
                    .noneMatch(f -> Arrays.asList(f.getAssignee().split(",")).contains(account));
            logger.info("查看流程检查权限-检查历史节点，pid:{}, tid:{}, acc:{}, notAbout:{}", flowInfo.getProcessId(), flowInfo.getCurrentTaskId(), account, notAbout);
        }
        //若历史不相关，再检查当前节点
        if (notAbout && CollectionUtils.isNotEmpty(flowInfo.getCurrentTaskInfoDtoList())) {
            notAbout = flowInfo.getCurrentTaskInfoDtoList()
                    .stream()
                    .noneMatch(f -> f.getCurrentAssigneeList().contains(account));
            logger.info("查看流程检查权限-检查当前节点，pid:{}, tid:{}, acc:{}, notAbout:{}", flowInfo.getProcessId(), flowInfo.getCurrentTaskId(), account, notAbout);
        }

        //审批节点时，没有历史节点、当前节点列表，需要对当前审批人检查
        if (notAbout && Strings.isNotBlank(flowInfo.getCurrentAssignee())) {
            notAbout = Arrays.stream(flowInfo.getCurrentAssignee().split(","))
                    .noneMatch(a -> a.equals(account));
            logger.info("查看流程检查权限-检查当前审批人，pid:{}, tid:{}, acc:{}, notAbout:{}", flowInfo.getProcessId(), flowInfo.getCurrentTaskId(), account, notAbout);
        }

        //兼容旧逻辑，抄送时无代办人，去具体表查询
        if (notAbout) {
            FlowableResponse<ResponseCopyUserDto> copyProcessOfUser = iFeignFlowableService.getCopyProcessOfUser(ParamCopyUserDto.builder()
                    .processId(flowInfo.getProcessId())
                    .userId(account)
                    .sysid(PlatformConstant.SYS_ID_GATEWAY)
                    .build());
            notAbout = Objects.isNull(copyProcessOfUser.getData()) || Objects.isNull(copyProcessOfUser.getData().getProcessId());
            logger.info("查看流程检查权限-检查抄送，pid:{}, tid:{}, acc:{}, notAbout:{}", flowInfo.getProcessId(), flowInfo.getCurrentTaskId(), account, notAbout);
        }
        logger.debug("检查用户与流程审批权限：{}-{},是否有权限=", account, flowInfo.getCurrentTaskId(), notAbout);
        return notAbout;
    }

    /**
     * 通过方法参数获取processId和taskId，并设置经办人/待办人
     *
     * @return Pair<processId, taskId>
     */
    private Pair<String, String> buildParam(Object[] args) {
        String processId = null, taskId = null;
        for (Object arg : args) {
            if (arg instanceof AbstractCreateParamDto) {
                //创建流程（不包括还未创建流程时，保存草稿的场景）
                AbstractCreateParamDto<?, ?> param = (AbstractCreateParamDto<?, ?>) arg;
                processId = param.getProcessId();
                taskId = param.getTaskId();
            } else if (arg instanceof AbstractPassParamDto) {
                //审批通过
                AbstractPassParamDto<?, ?> param = (AbstractPassParamDto<?, ?>) arg;
                processId = param.getProcessId();
                taskId = param.getTaskId();
            } else if (arg instanceof AbstractRejectParamDto) {
                //审批驳回
                AbstractRejectParamDto param = (AbstractRejectParamDto) arg;
                processId = param.getProcessId();
                taskId = param.getTaskId();
            } else if (arg instanceof AbstractFindByIdParamDto) {
                //查看详情
                AbstractFindByIdParamDto param = (AbstractFindByIdParamDto) arg;
                processId = param.getProcessId();
                taskId = param.getTaskId();
            } else if (arg instanceof ParamStopDto) {
                //强制结束
                ParamStopDto param = (ParamStopDto) arg;
                processId = param.getProcessId();
            }
            if (Strings.isNotBlank(processId) || Strings.isNotBlank(taskId)) {
                break;
            }
        }
        return new Pair<>(processId, taskId);
    }
}
